/**
 * Alibaba.com.cn Inc.
 * Copyright (c) 2004-2021 All Rights Reserved.
 */
package com.smart.utils;

import cn.hutool.core.thread.ExecutorBuilder;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.comm.io.BoundedInputStream;
import com.aliyun.oss.model.AbortMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadRequest;
import com.aliyun.oss.model.InitiateMultipartUploadResult;
import com.aliyun.oss.model.PartETag;
import com.aliyun.oss.model.UploadPartRequest;
import com.aliyun.oss.model.UploadPartResult;
import com.smart.config.OssProperties;
import com.smart.controller.UploadPartTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;

/**
 *
 * @author
 */
@Slf4j
@Component
public class OSSUtil {
    /**
     * 分片大小为 5MB
     */
    private static Integer partSize = 1024 * 1024 * 2;

    /**
     * 单例
     */
    public static OSSUtil instance;

    @Autowired
    private OSS oss;
    @Autowired
    private OSSClient ossClient;
    @Resource
    private OssProperties ossProperties;

    private ExecutorService executorService = ExecutorBuilder.create().setCorePoolSize(5).setMaxPoolSize(200).setWorkQueue(new LinkedBlockingDeque<>(100000)).build();

    private OSSUtil() {}

    public static String uploadMultipartFile(MultipartFile multipartFile, String objectName) throws IOException {
        log.info("uploadMultipartFile... objectName={}", objectName);
        long fileLength = multipartFile.getSize();
        List<PartETag> partETags = new ArrayList<>();
        // 初始化 InitiateMultipartUploadRequest
        InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(instance.ossProperties.getBucketName(), objectName);
        InitiateMultipartUploadResult initResponse = instance.oss.initiateMultipartUpload(initRequest);
        try {
            ByteArrayResource resource = new ByteArrayResource(StreamUtils.copyToByteArray(multipartFile.getInputStream())) {
                @Override
                public String getFilename() {
                    return multipartFile.getOriginalFilename();
                }
            };
            InputStream inputStream = resource.getInputStream();
            long uploadedBytes = 0;
            for(int i = 1; uploadedBytes < fileLength; i++) {
                long partSizeTemp = Math.min(partSize, fileLength - uploadedBytes);
                UploadPartRequest uploadRequest = new UploadPartRequest();
                uploadRequest.setBucketName(instance.ossProperties.getBucketName());
                uploadRequest.setKey(objectName);
                uploadRequest.setUploadId(initResponse.getUploadId());
                uploadRequest.setPartNumber(i);
                uploadRequest.setInputStream(inputStream);
                uploadRequest.setPartSize(partSizeTemp);
                log.info("uploadMultipartFile... 分片数：{}", i);
                UploadPartResult uploadResult = instance.oss.uploadPart(uploadRequest);
                partETags.add(uploadResult.getPartETag());
                uploadedBytes += partSizeTemp;
            }
            // 完成分片上传
            log.info("分片完成.... 组装分片");
            CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(instance.ossProperties.getBucketName(), objectName, initResponse.getUploadId(), partETags);
            instance.oss.completeMultipartUpload(compRequest);

            // 返回访问链接 https://daxingjianzhu.oss-cn-beijing.aliyuncs.com/16%E7%A7%92-38M.MP4
            return StrUtil.format("{}/{}", instance.ossProperties.getDomain(), objectName);
        } catch(IOException e) {
            // 取消分片上传
            AbortMultipartUploadRequest abortRequest = new AbortMultipartUploadRequest(instance.ossProperties.getBucketName(), objectName, initResponse.getUploadId());
            instance.oss.abortMultipartUpload(abortRequest);
            throw e;
        }
    }

    public String uploadMultipartFile3(MultipartFile multipartFile, String objectName) throws IOException, InterruptedException, ExecutionException {
        log.info("uploadMultipartFile... objectName={}", objectName);
        long fileLength = multipartFile.getSize();
        List<PartETag> partETags = new ArrayList<>();
        // 初始化 InitiateMultipartUploadRequest
        InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(instance.ossProperties.getBucketName(), objectName);
        InitiateMultipartUploadResult initResponse = instance.oss.initiateMultipartUpload(initRequest);

        try {
            int partNumber = 1;
            long uploadedBytes = 0;
            while (uploadedBytes < fileLength) {
                long partSizeTemp = Math.min(partSize, fileLength - uploadedBytes);
                InputStream partInputStream = getFilePartInputStream(multipartFile, partSizeTemp, uploadedBytes);
                UploadPartCallable uploadPartCallable = new UploadPartCallable(instance.ossProperties.getBucketName(),
                        objectName, initResponse.getUploadId(), partNumber, partInputStream, partSizeTemp,
                        instance.ossClient);
                Future<PartETag> future = executorService.submit(uploadPartCallable);
                partETags.add(future.get()); // Collect the PartETags from each future
                uploadedBytes += partSizeTemp;
                partNumber++;
            }

            // 完成分片上传
            log.info("分片完成.... 组装分片");
            CompleteMultipartUploadRequest compRequest = new CompleteMultipartUploadRequest(instance.ossProperties.getBucketName(), objectName, initResponse.getUploadId(), partETags);
            instance.oss.completeMultipartUpload(compRequest);

            // 返回访问链接 https://daxingjianzhu.oss-cn-beijing.aliyuncs.com/16%E7%A7%92-38M.MP4
            return StrUtil.format("{}/{}", instance.ossProperties.getDomain(), objectName);
        } catch(IOException e) {
            // 取消分片上传
            AbortMultipartUploadRequest abortRequest = new AbortMultipartUploadRequest(instance.ossProperties.getBucketName(), objectName, initResponse.getUploadId());
            instance.oss.abortMultipartUpload(abortRequest);
            throw e;
        }
    }

    // Callable class for uploading a single part
    class UploadPartCallable implements Callable<PartETag> {
        private String bucketName;
        private String objectName;
        private String uploadId;
        private int partNumber;
        private InputStream inputStream;
        private long partSize;
        private OSSClient ossClient;

        public UploadPartCallable(String bucketName, String objectName, String uploadId, int partNumber, InputStream inputStream, long partSize, OSSClient ossClient) {
            this.bucketName = bucketName;
            this.objectName = objectName;
            this.uploadId = uploadId;
            this.partNumber = partNumber;
            this.inputStream = inputStream;
            this.partSize = partSize;
            this.ossClient = ossClient;
        }

        @Override
        public PartETag call() throws Exception {
            UploadPartRequest uploadRequest = new UploadPartRequest();
            uploadRequest.setBucketName(bucketName);
            uploadRequest.setKey(objectName);
            uploadRequest.setUploadId(uploadId);
            uploadRequest.setPartNumber(partNumber);
            uploadRequest.setInputStream(inputStream);
            uploadRequest.setPartSize(partSize);
            log.info("uploadMultipartFile... 分片数：{}", partNumber);
            UploadPartResult uploadResult = ossClient.uploadPart(uploadRequest);
            return uploadResult.getPartETag();
        }
    }

    // Helper method to get the input stream for a file part
    private static InputStream getFilePartInputStream(MultipartFile multipartFile, long partSize, long uploadedBytes) throws IOException {
        InputStream inputStream = multipartFile.getInputStream();
        inputStream.skip(uploadedBytes); // Skip to the starting position of the part
        return new BoundedInputStream(inputStream, partSize); // Wrap the input stream with a BoundedInputStream to limit the number of bytes read
    }

    /**
     * Init.
     */
    @PostConstruct
    public void init() {
        instance = this;
    }
}

